/*
 * (c) ralfoide, http://gamez.googlecode.com/, 2008
 * Project: gamez
 * License TBD
 */


package com.alfray.gamez;

import java.lang.Thread.State;
import java.util.HashMap;

import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.Menu.Item;
import android.widget.TextView;

import com.alfray.gamez.gameplay.Gameplay;
import com.alfray.gamez.gameplay.Shooter;
import com.alfray.gamez.gameplay.Title;

//-----------------------------------------------

/**
 * The main activity of Game!z.
 * 
 * Setups the UI and takes care of the Andriod activity life cycle.
 */
public class GamezActivity extends Activity {

	public static enum GameplayId {
		Title(0),
		Shooter(1);
		
		private int mId;
		private GameplayId(int id) { mId = id; }
		public int getId() { return mId; }
	}

	private static final GameplayId START_ID = GameplayId.Shooter;

	/** The global game!z context, with references to all current instances */
	public class Context {
		/** The SurfaceView's holder */
		public SurfaceHolder mSurfaceHolder;
		/** Rendering thread. Calls the renderer at regular intervals. */
		public RenderThread mRenderThread;
		/** The current instance of the "gameplay" */
		public Gameplay mGameplay;
		/** Startup time of the "gameplay" */
		public long mStartTime;
		/** Increased each time a GL update occurs */
		public int mUpdates;
		/** Time spend in the last rendering in milliesconds */
		public long mRenderTimeMs;
		/** Total time spend doing rendering since last fps update.
		 *  Used to provide an estimated FPS. */
		public long mTotalRenderTimeMs;
		/** Time spent in the GLContext.post() method */
		public long mPostTimeMs;
		/** Android application context (for resource access) */
		public android.content.Context mAndroidContext;
		/** GL error counter */
		public long mErrorCount;
		/** Time spent in the last gameplay thread loop iteration */
		public long mGameplayThreadMs;
		
		/** Switches the current gameplay.
		 * <p/>
		 * The gameplay is switched via a pending task in the render thread, to make
		 * sure we don't switch gameplays in middle of a frame.
		 * @param id One of the GamezActivity.GameplayId constants.
		 * @return True if the gameplay was switched 
		 */
		public boolean switchGameplay(GameplayId id) {
			return selectGameplay(id);
		}
	}

    private static final String TAG = "Gamez.Activity";

    /** text view for fps info */
	TextView mTextView;
	/** GL surface view */
	private GLSurfaceView mSurfaceView;
	/** Context for our game */
	private Context mContext;
	/** Handler for posting delay requests to update fps */
	private Handler mHandler;
	/** Runnable that updates the fps text in the UI thread */
	private UpdateFps mFpsUpdater;
	/** Cache of already instantiated gameplays */
	private HashMap<GameplayId, Gameplay> mGameplays;
	
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle icicle) {
	    super.onCreate(icicle);

	    mContext = new Context();
	    mContext.mAndroidContext = this; 
	    mHandler = new Handler();
	    mGameplays = new HashMap<GameplayId, Gameplay>();

        setupViews();
	}

	/** Called when the activity is being resumed (after create, freeze or pause) */
	@Override
	protected void onResume() {
		super.onResume();
	}

	/** Called when the system needs to pause the activity (but not freeze it yet) */
	@Override
	protected void onPause() {
		super.onPause();
		pause(true);
	}

	/** Called when the system needs to stop (aka purge) the activity */
	@Override
	protected void onStop() {
		super.onStop();
		stop();
	}
	
	// TODO: implement onSave... or onFreeze()
	
	/** Called when the system detected a configuration change and we indicated
	 * we can handle it via AndroidManifest.xml > app > activity > configChange.  
	 * <p/>
	 * In this case we say we can handle screen rotation so we need to
	 * perform the view setup again.
	 */
	@Override
	protected void onConfigurationChanged(Configuration newConfig) {
		setupViews();
		super.onConfigurationChanged(newConfig);
	}

	/** Called to create the options menu once.
	 * <p/>
	 * The menu in M5 has one row with 2 buttons and a second row with 3
	 * buttons. If there are more buttons, it will have a "More" icon.
	 */
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		Item i;
		
		i = menu.add(0 /* group */,
				Menu.FIRST + START_ID.getId(),
				0 /* order */,
				"Title");
		i.setShortcut('1', 't');
		i.setIcon(R.drawable.famfamfam_bricks);

		i = menu.add(0 /* group */,
				Menu.FIRST + GameplayId.Shooter.getId(),
				0 /* order */,
				"Shooter");
		i.setShortcut('2', 's');
		i.setIcon(R.drawable.wain_arrow_up);

		return super.onCreateOptionsMenu(menu);
	}

	/** Called by the system when a menu item is selected.
	 * <p/>
	 *  Simply switch to the adequate gameplay.
	 */
	@Override
	public boolean onOptionsItemSelected(Item item) {
		int id = item.getItemId();
		if (id == Menu.FIRST + START_ID.getId()) {
			selectGameplay(START_ID);
		} else if (id == Menu.FIRST + GameplayId.Shooter.getId()) {
			selectGameplay(GameplayId.Shooter);
		}
		
		return super.onOptionsItemSelected(item);
	}
	

	//------------------------------------------

	/**
	 * Setup view layout and get the references to our view widgets.
	 * <p/>
	 * Also attach whatever listeners we need to the views.
	 */
	private void setupViews() {
		
		pause(true);
		
		setContentView(R.layout.gamez);

		mTextView = (TextView) findViewById(R.id.info);
	    mTextView.setText("Game!z");
	    
	    mSurfaceView = (GLSurfaceView) findViewById(R.id.surface);
	    mContext.mSurfaceHolder = mSurfaceView.getHolder();
	    mContext.mSurfaceHolder.addCallback(new SurfaceViewCallback());
	    mContext.mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
	    
	    mSurfaceView.requestFocus();
	}
	
	/**
	 * Select a gameplay, using one of the menu ids.
	 * <p/>
	 * The gameplay instance is cached in a map. Reuse it if already present.
	 * The gameplay is switched via a pending task in the render thread, to make
	 * sure we don't switch gameplays in middle of a frame.
	 *
	 * @param gameplay_id One of the MENU_ID_GAME_ constants
	 * @return True if the gameplay id was switched, false otherwise.
	 */
	private boolean selectGameplay(GameplayId gameplay_id) {
		if (mContext != null) {
			Gameplay gp = mGameplays.get(gameplay_id);
			if (gp == null) {
				switch(gameplay_id) {
				case Title:
					gp = new Title(mContext);
					break;
				case Shooter:
					gp = new Shooter(mContext);
					break;
				default:
					// not a valid id, do nothing
					return false;
				}
				mGameplays.put(gameplay_id, gp);
			}
			
			if (mContext.mGameplay != gp && mContext.mRenderThread != null) {
				final Gameplay new_gameplay = gp;
				mContext.mRenderThread.addPendingTask(new Runnable() {
					public void run() {
						mSurfaceView.setUiEventHandler(new_gameplay);
						mContext.mGameplay = new_gameplay;
						new_gameplay.requestSetupScene();
						new_gameplay.pauseThread(false);
					}
				});
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Starts the rendering thread, the gameplay and the message updater.
	 * <p/>
	 * Does nothing if they are already started.
	 */
	private void start() {
		if (mContext.mRenderThread == null) {
			mContext.mRenderThread = new RenderThread(mContext);
		}
		if (mContext.mGameplay == null) {
			// Select which gameplay to start with
			selectGameplay(START_ID);
		}

		if (mContext.mRenderThread.getState() == State.NEW) {
			mContext.mRenderThread.start();
		}

		if (mFpsUpdater == null) {
		    mFpsUpdater = new UpdateFps();
		    mHandler.postDelayed(mFpsUpdater, 1000 /* ms */);
		}
	}

	/**
	 * Stops the gameplay and rendering thread.
	 * <p/>
	 * The gameplay is stopped but the instance is not removed.
	 * The rendering thread is stopped and the instance is discarded.
	 */
	private void stop() {
		mFpsUpdater = null;

		if (mContext.mGameplay != null) {
			mContext.mGameplay.stopThread();
		}

		if (mContext.mRenderThread != null) {
			mContext.mRenderThread.waitForStop();
			mContext.mRenderThread = null;
		}
	}

	/**
	 * Toggles pause on/off on the rendering and gameplay threads. 
	 * 
	 * @param pause True to pause the threads, false to start them again.
	 */
	private void pause(boolean pause) {
		if (mContext.mGameplay != null) {
			mContext.mGameplay.pauseThread(pause);
		}

		if (mContext.mRenderThread != null) {
			mContext.mRenderThread.pauseThread(pause);
		}
		
		if (pause == true) {
			// make sure they got time to pause
			try {
				Thread.sleep(100 /* ms */);
			} catch (InterruptedException e) {
				Log.e(TAG, "Thread.sleep interrupted", e);
			}
		}
	}

	/**
	 * Callback when the surface view is clicked
	 */
	protected void surfaceClicked() {
		assert mContext != null;
		if (mContext.mGameplay != null) {
			//mContext.mGameplay.toogleRender();
		}
	}


	//------------------------------------------

	/**
	 * Runnable that updates the FPS & timing info in the screen's text view.
	 * <p/>
	 * Updates are done in the main thread about once every second.
	 */
	class UpdateFps implements Runnable {
		public void run() {
			if (mFpsUpdater == null) {
				return;
			}

			if (mContext != null && mTextView != null) {
				
				String message = (mContext.mGameplay != null) ? mContext.mGameplay.getMessage() : "";
				
				// Note: we don't lock the rendering thread so it might be updating
				// these counters at the same time. So clear them as close to them
				// being captured as possible. It's not perfect but it's good enough.
				int num_updates = mContext.mUpdates;
				mContext.mUpdates = 0;
				long total_render_ms = mContext.mTotalRenderTimeMs;
				mContext.mTotalRenderTimeMs = 0;
				// estimated fps
				float fps =  1000f / total_render_ms * num_updates;
				mTextView.setText(String.format(
						"%s\n%2.0f fps, Rdr % 2d ms, Post % 2d ms, T %03d ms\nG % 2d ms, Err %d%s",
						message,
						fps, mContext.mRenderTimeMs, mContext.mPostTimeMs,
						total_render_ms,
						mContext.mGameplayThreadMs,
						mContext.mErrorCount, mContext.mRenderThread.getLastError()));
			}
		    mHandler.postDelayed(mFpsUpdater, 1000 /* ms */);
		}
	}


	//------------------------------------------

	/**
	 * Callback for the SurfaceView.
	 * <p/>
	 * It basically starts everything when the view is created and makes sure everything
	 * is stopped before the view is going to be destroyed.
	 * <p/>
	 * Note that when the activity is created, the surface view is created and
	 * this is what starts everything.
	 */
	class SurfaceViewCallback implements SurfaceHolder.Callback {

		/**
		 * Tell GL to resize its viewport if the view is resized.
		 */
		public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
			assert mContext != null;
			if (mContext.mRenderThread != null) {
				mContext.mRenderThread.onWindowResize(width, height);
			}
		}

		/**
		 * Starts the thread once the surface view has been created.
		 * <p/>
		 * This is what makes the game start.
		 */
		public void surfaceCreated(SurfaceHolder holder) {
			start();
			pause(false);
		}

		/**
		 * Stops the threads before the surface view is destroyed.
		 */
		public void surfaceDestroyed(SurfaceHolder holder) {
			stop();
		}		
	}
}